package com.backend.server.service.impl;

import com.backend.server.license.LicenseCheckModel;
import com.backend.server.service.ProjectInfoService;
import com.backend.server.service.UserInfoService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.common.server.common.ErrorCode;
import com.common.server.constant.LicenseConstant;
import com.backend.server.license.LicenseCreatorParam;
import com.common.server.entity.license.LicenseInfo;
import com.backend.server.license.LicenseCreatorParamRequest;
import com.common.server.entity.license.vo.LicenseContentVO;
import com.common.server.entity.project.ProjectInfo;
import com.common.server.entity.user.UserInfo;
import com.common.server.enums.UserRoleEnum;
import com.common.server.exception.ThrowUtils;
import com.common.server.mapper.LicenseInfoMapper;
import com.google.gson.Gson;
import com.backend.server.license.CustomKeyStoreParam;
import com.backend.server.service.LicenseCreateService;
import de.schlichtherle.license.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import java.util.prefs.Preferences;
import java.util.regex.Pattern;

/**
 * ClassName: LicenseCreateServiceImpl
 * Package: com.memory.licenseserver.service.impl
 * Description:
 *
 * @Author Memory
 * @Create 2024/12/20 11:24
 * @Version 1.0
 */
@Service
@Slf4j
public class LicenseCreateServiceImpl extends ServiceImpl<LicenseInfoMapper, LicenseInfo> implements LicenseCreateService {
    @Resource
    private UserInfoService userInfoService;
    @Resource
    private ProjectInfoService projectInfoService;
    // 证书生成参数
    @Resource
    private LicenseCreatorParam param;
    // 失效时间
    int validityDays = 0;
    // 证书信息
    private final LicenseInfo licenseInfo = new LicenseInfo();
    // 证书序号
    String licenseID = "";

    // IPv4地址的正则表达式
    private static final String IPV4_REGEX =
            "^([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])" +
                    "(\\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}$";


    private static final Pattern IPV4_PATTERN = Pattern.compile(IPV4_REGEX);
    // MAC地址的正则表达式（支持冒号、连字符和点分隔）
    private static final String MAC_REGEX =
            "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$|" + // 冒号或连字符分隔
                    "^([0-9A-Fa-f]{2}\\.){5}([0-9A-Fa-f]{2})$";    // 点分隔
    private static final Pattern MAC_PATTERN = Pattern.compile(MAC_REGEX);

    // CPU序列号（或主板序列号）的正则表达式（宽松匹配）
    // 假设序列号由字母、数字、连字符、点和空格组成，长度在10到40个字符之间
    private static final String SERIAL_REGEX =
            "^[A-Za-z0-9\\-.\\s]{8,40}$";
    private static final Pattern SERIAL_PATTERN = Pattern.compile(SERIAL_REGEX);


    /**
     * 生成 License 证书
     *
     * @return boolean
     */
    public LicenseContentVO generateLicense(LicenseCreatorParamRequest param, HttpServletRequest request) {
        // 1.用户权限
        UserInfo userInfo = userInfoService.getLoginUser(request);
        ThrowUtils.throwIf(ObjectUtils.isEmpty(userInfo), ErrorCode.NOT_LOGIN_ERROR, "用户未登录");
        ThrowUtils.throwIf(!userInfo.getUserRole().equals(UserRoleEnum.ADMIN.getValue()), ErrorCode.NO_AUTH_ERROR, "非管理员无权限生成证书");
        // 2.校验并设置证书生成参数
        Boolean validated = this.validateAndSetParam(param);
        ThrowUtils.throwIf(!validated, ErrorCode.PARAMS_ERROR, "证书生成参数校验失败");
        // 3.生成并保存证书
        LicenseContentVO licenseContentVO = this.generate();
        ThrowUtils.throwIf(ObjectUtils.isEmpty(licenseContentVO), ErrorCode.OPERATION_ERROR, "证书生成失败");
        return licenseContentVO;
    }

    /**
     * 校验并设置证书生成参数
     *
     * @param param 参数
     * @throws IllegalArgumentException 如果参数校验失败
     */
    private Boolean validateAndSetParam(LicenseCreatorParamRequest param) throws IllegalArgumentException {
        // 检查所属项目是否为空
        Long projectId = param.getProjectId();
        ThrowUtils.throwIf(ObjectUtils.isEmpty(projectId), ErrorCode.PARAMS_ERROR, "所属项目ID不能为空");
        int count = projectInfoService.count(new QueryWrapper<ProjectInfo>().eq("id", projectId));
        ThrowUtils.throwIf(count == 0, ErrorCode.PARAMS_ERROR, "所属项目不存在");
        // 检查申请人姓名是否为空
        String applicantName = param.getApplicantName();
        ThrowUtils.throwIf(StringUtils.isBlank(applicantName), ErrorCode.PARAMS_ERROR, "申请人姓名不能为空");
        ThrowUtils.throwIf(applicantName.length() > LicenseConstant.APPLICANT_NAME, ErrorCode.PARAMS_ERROR, "申请人姓名长度不能超过10个字符");
        // 检查证书描述是否为空
        String licenseName = param.getLicenseName();
        ThrowUtils.throwIf(StringUtils.isBlank(licenseName), ErrorCode.PARAMS_ERROR, "证书名不能为空");
        ThrowUtils.throwIf(licenseName.length() > LicenseConstant.LICENSE_NAME, ErrorCode.PARAMS_ERROR, "证书名长度不能超过30个字符");
        // 检查证书有效期天数是否为空
        this.validityDays = param.getValidityDays();
        ThrowUtils.throwIf(ObjectUtils.isEmpty(this.validityDays), ErrorCode.PARAMS_ERROR, "证书有效期天数不能为空");
        ThrowUtils.throwIf(this.validityDays < LicenseConstant.VALIDITY_DAYS_MIN, ErrorCode.PARAMS_ERROR, "证书有效期至少为7天");
        ThrowUtils.throwIf(this.validityDays > LicenseConstant.VALIDITY_DAYS_MAX, ErrorCode.PARAMS_ERROR, "证书有效期不能超过365天");
        // 计算生效时间和失效时间
        LocalDateTime issuedTime = LocalDateTime.now();
        LocalDateTime expirationTime = issuedTime.plusDays(this.validityDays);
        // 检查失效时间是否早于或等于当前时间
        ThrowUtils.throwIf(expirationTime.isBefore(issuedTime) || expirationTime.isEqual(issuedTime), ErrorCode.PARAMS_ERROR, "证书失效时间不能早于或等于生效时间");
        LicenseCheckModel licenseCheckModel = param.getLicenseCheckModel();
        // 检查服务器硬件信息是否为空
        ThrowUtils.throwIf(ObjectUtils.isEmpty(licenseCheckModel), ErrorCode.PARAMS_ERROR, "服务器硬件信息不能为空");
        List<String> ipAddress = licenseCheckModel.getIpAddress();
        List<String> macAddress = licenseCheckModel.getMacAddress();
        String cpuSerial = licenseCheckModel.getCpuSerial();
        String mainBoardSerial = licenseCheckModel.getMainBoardSerial();

        ThrowUtils.throwIf(ObjectUtils.isEmpty(cpuSerial), ErrorCode.PARAMS_ERROR, "CPU序列号不能为空");
        ThrowUtils.throwIf(ObjectUtils.isEmpty(mainBoardSerial), ErrorCode.PARAMS_ERROR, "主板序列号不能为空");
        ThrowUtils.throwIf(ObjectUtils.isEmpty(ipAddress), ErrorCode.PARAMS_ERROR, "可允许的IP地址不能为空");
        ThrowUtils.throwIf(ObjectUtils.isEmpty(macAddress), ErrorCode.PARAMS_ERROR, "可允许的MAC地址不能为空");

        ThrowUtils.throwIf(!ipAddress.stream().allMatch(ip -> IPV4_PATTERN.matcher(ip).matches()), ErrorCode.PARAMS_ERROR, "IP地址格式错误");
        ThrowUtils.throwIf(!macAddress.stream().allMatch(mac -> MAC_PATTERN.matcher(mac).matches()), ErrorCode.PARAMS_ERROR, "MAC地址格式错误");
        ThrowUtils.throwIf(!SERIAL_PATTERN.matcher(cpuSerial).matches(), ErrorCode.PARAMS_ERROR, "CPU序列号格式错误");
        ThrowUtils.throwIf(!SERIAL_PATTERN.matcher(mainBoardSerial).matches(), ErrorCode.PARAMS_ERROR, "主板序列号格式错误");

        // 设置证书生成参数
        this.param.setDescription(licenseName);
        this.param.setIssuedTime(issuedTime);
        this.param.setExpiryTime(expirationTime);
        // 获取配置文件
        Properties prop = new Properties();
        try {
            prop.load(LicenseCreator.class.getResourceAsStream("/application.properties"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        // 读取配置文件中的参数
        this.param.setSubject(prop.getProperty("license.subject"));
        this.param.setPrivateAlias(prop.getProperty("license.private-alias"));
        this.param.setKeyPass(prop.getProperty("license.key-pass"));
        this.param.setStorePass(prop.getProperty("license.store-pass"));

        licenseID = UUID.randomUUID().toString();
        this.param.setLicensePath(Paths.get(LicenseConstant.USER_DIR, prop.getProperty("license.license-path"), licenseID + ".lic").toString());
        this.param.setPrivateKeysStorePath(Paths.get(LicenseConstant.USER_DIR, prop.getProperty("license.private-keys-store-path")).toString());
        this.param.setConsumerType(prop.getProperty("license.consumer-type"));
        this.param.setConsumerAmount(Integer.parseInt(prop.getProperty("license.consumer-amount")));
        // 扩展校验服务器硬件信息
        this.param.setLicenseCheckModel(licenseCheckModel);
        // 保存证书信息_1
        licenseInfo.setLicenseName(licenseName);
        licenseInfo.setProjectId(param.getProjectId());
        licenseInfo.setApplicantName(param.getApplicantName());
        licenseInfo.setValidityDays(this.validityDays);
        return true;
    }

    /**
     * 生成 License 证书
     *
     * @return 生成结果
     */
    public LicenseContentVO generate() {
        // 1.初始化证书生成参数
        LicenseParam licenseParam = this.initLicenseParam();
        // 2.初始化证书生成正文信息
        LicenseContent licenseContent = this.initLicenseContent();
        // 3.生成证书
        LicenseManager licenseManager = new LicenseManager(licenseParam);
        try {
            licenseManager.store(licenseContent, new File(this.param.getLicensePath()));
            log.info("证书生成成功");
            log.info("证书路径：{}", this.param.getLicensePath());
        } catch (Exception e) {
            log.error(MessageFormat.format("证书生成失败：{0}", param), e.getMessage());
        }
        // 4.保存证书信息_2
        Gson gson = new Gson();
        licenseInfo.setId(licenseID);
        licenseInfo.setLicenseName(this.param.getDescription());
        licenseInfo.setIpAddress(gson.toJson(this.param.getLicenseCheckModel().getIpAddress()));
        licenseInfo.setMacAddress(gson.toJson(this.param.getLicenseCheckModel().getMacAddress()));
        licenseInfo.setCpuSerial(this.param.getLicenseCheckModel().getCpuSerial());
        licenseInfo.setMainBoardSerial(this.param.getLicenseCheckModel().getMainBoardSerial());
        licenseInfo.setIssuedTime(this.convertTimeFormat(param.getIssuedTime()));
        licenseInfo.setExpiryTime(this.convertTimeFormat(param.getExpiryTime()));
        licenseInfo.setValidityDays(this.validityDays);
        boolean save = this.save(licenseInfo);
        ThrowUtils.throwIf(!save, ErrorCode.OPERATION_ERROR, "证书信息保存失败");
        // 5.返回结果
        LicenseContentVO licenseContentVO = new LicenseContentVO();
        BeanUtils.copyProperties(licenseContent, licenseContentVO);
        return licenseContentVO;
    }

    /**
     * 初始化证书生成参数
     *
     * @return de.schlichtherle.license.LicenseParam
     */
    private LicenseParam initLicenseParam() {
        // 获取用户偏好
        Preferences preferences = Preferences.userNodeForPackage(LicenseCreator.class);
        // 配置私钥存储参数
        KeyStoreParam privateStoreParam = new CustomKeyStoreParam(LicenseCreator.class
                , param.getPrivateKeysStorePath()   // 私钥存储路径
                , param.getPrivateAlias()           // 私钥别名
                , param.getStorePass()              // 存储密码(可能也用于私钥加密)
                , param.getKeyPass());              // 私钥密码(如果与存储密码不同)
        // 设置对证书内容加密的密钥参数
        CipherParam cipherParam = new DefaultCipherParam(param.getStorePass());
        // 返回证书生成参数
        return new DefaultLicenseParam(param.getSubject()
                , preferences
                , privateStoreParam
                , cipherParam);
    }

    /**
     * 设置证书生成正文信息
     *
     * @return de.schlichtherle.license.LicenseContent
     */
    private LicenseContent initLicenseContent() {
        // 设置证书正文信息
        LicenseContent licenseContent = new LicenseContent();
        licenseContent.setHolder(LicenseConstant.DEFAULT_HOLDER_AND_ISSUER);
        licenseContent.setIssuer(LicenseConstant.DEFAULT_HOLDER_AND_ISSUER);
        licenseContent.setSubject(this.param.getSubject());
        licenseContent.setInfo(this.param.getDescription());
        licenseContent.setConsumerType(this.param.getConsumerType());
        licenseContent.setConsumerAmount(this.param.getConsumerAmount());
        // 转换时间格式
        licenseContent.setIssued(this.convertTimeFormat(this.param.getIssuedTime()));
        licenseContent.setNotBefore(this.convertTimeFormat(this.param.getIssuedTime()));
        licenseContent.setNotAfter(this.convertTimeFormat(this.param.getExpiryTime()));
        // 扩展校验服务器硬件信息
        licenseContent.setExtra(this.param.getLicenseCheckModel());
        return licenseContent;
    }

    /**
     * 转换时间格式
     */
    private Date convertTimeFormat(LocalDateTime localDateTime) {
        // 使用系统默认时区将 LocalDateTime 转换为 ZonedDateTime
        ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault());
        // 将 ZonedDateTime 转换为 Date
        return Date.from(zonedDateTime.toInstant());
    }
}
